package com.example.controllers;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.example.ShoppingCartApiApplication;
import com.example.dtos.CartDTO;
import com.example.dtos.CustomerDTO;
import com.example.dtos.ItemToPurchaseDTO;
import com.example.models.Cart;
import com.example.models.Customer;
import com.example.models.ItemToPurchase;
import com.example.models.Product;
import com.example.repositories.CartRepository;
import com.example.repositories.CustomerRepository;
import com.example.repositories.ProductRepository;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ShoppingCartApiApplication.class)
@WebAppConfiguration //tells JUnit that this is a unit test for Spring MVC web components and should thus 
//run under a WebApplicationContext variety, not a standard ApplicationContext implementation.
@IntegrationTest({"server.port=9090"})
public class CartControllerIT {
	
	private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(),
            Charset.forName("utf8"));

    private MockMvc mockMvc;
    
    private HttpMessageConverter mappingJackson2HttpMessageConverter;

    private Cart cart1,cart2;
    CartDTO cartDTO1,cartDTO2;
    private Customer customer,customer2;
    CustomerDTO customerDTO, customerDTO2;
    Set<ItemToPurchase> itemList = new HashSet<>();
    Set<ItemToPurchase> itemList2 = new HashSet<>();
    Set<ItemToPurchaseDTO> itemListDTO1,itemListDTO2;
    ItemToPurchase item1,item2,item3;
    ItemToPurchaseDTO itemDTO1,itemDTO2,itemDTO3;
    
    @Autowired
    private CartRepository cartRepository;
    @Autowired
    private CustomerRepository customerRepository;
    @Autowired
    private ProductRepository productRepository;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Autowired
    void setConverters(HttpMessageConverter<?>[] converters) {

        this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(
                hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();

        Assert.assertNotNull("the JSON message converter must not be null",
                this.mappingJackson2HttpMessageConverter);
    }
    
    @Before
    public void setup() throws Exception {
        this.mockMvc = webAppContextSetup(webApplicationContext).build();

        this.cartRepository.deleteAll();
        this.productRepository.deleteAll();
        this.customerRepository.deleteAll();
        
        customer = customerRepository.save(new Customer("customerId", "username", "firstname", "lastname"));
        customer2 = customerRepository.save(new Customer("customer2Id", "username2", "firstname2", "lastname2"));
        
        Product p1 = productRepository.save(new Product("p1Id",100));
        Product p2 = productRepository.save(new Product("p2Id",100));
        
        item1 = new ItemToPurchase("it1Id",p1.getId(),10,cart1);
        item2 = new ItemToPurchase("it2Id",p2.getId(),15,cart2);
        item3 = new ItemToPurchase("it3Id",p1.getId(),25,cart1);
        
        itemList.add(item1);
        itemList.add(item2);
        itemList2.add(item3);
        
        this.cart1 = cartRepository.save(new Cart("c1Id",itemList, customer));
        this.cart2 = cartRepository.save(new Cart("c2Id",itemList2, customer2));
        
        customerDTO = new CustomerDTO("customerId", "username", "firstname", "lastname");
		customerDTO2 = new CustomerDTO("customer2Id", "username2", "firstname2", "lastname2");
	     
	     itemDTO1 = new ItemToPurchaseDTO("it1Id",p1.getId(),10);
	     itemDTO2 = new ItemToPurchaseDTO("it2Id",p2.getId(),15);
	     itemDTO3 = new ItemToPurchaseDTO("it3Id",p1.getId(),25);
	     itemListDTO1 = new HashSet<ItemToPurchaseDTO>();
	     itemListDTO2 = new HashSet<ItemToPurchaseDTO>();
	     itemListDTO1.add(itemDTO1);
	     itemListDTO1.add(itemDTO2);
	     itemListDTO2.add(itemDTO3);
	     
	     this.cartDTO1 = new CartDTO("c1Id",itemListDTO1, customerDTO);
	     this.cartDTO2 = new CartDTO("c2Id",itemListDTO2, customerDTO2);
    }
    
    
	@Test
	public void cartNotFound() throws Exception{
		 mockMvc.perform(get("/carts/123")
		 			.contentType(contentType))
	                .andExpect(status().isNotFound());
	}
	
	@Test
    public void readSingleCart() throws Exception {
        mockMvc.perform(get("/carts/"
                + this.cartDTO1.getId()))
                .andExpect(status().isOk())
                .andExpect(content().contentType(contentType))
                .andExpect(jsonPath("$.id", is(this.cartDTO1.getId())))
//                .andExpect(jsonPath("$.customer", is(this.json(this.cart.getCustomer()))))
                .andExpect(jsonPath("$.customer.id", is(this.cartDTO1.getCustomer().getId())))
        		.andExpect(jsonPath("$.items", hasSize(this.cartDTO1.getItems().size())));
    }
	
	@Test
	public void deleteCart() throws Exception {
		mockMvc.perform(delete("/carts/"
                + this.cartDTO1.getId()))
				.andExpect(status().isNoContent());
	}
	
	@Test
	public void deleteCartWhenNotFound() throws Exception {
		mockMvc.perform(delete("/carts/"
                + "123"))
				.andExpect(status().isNotFound());
	}
	
	@Test
	public void createCart() throws Exception {
	        String cartJson = json(new CartDTO(this.itemListDTO1,this.customerDTO));
	        this.mockMvc.perform(post("/carts")
	                .contentType(contentType)
	                .content(cartJson))
	                .andExpect(status().isCreated());
	}

	@Test
	public void createCartWithWrongCustomerFormat() throws Exception {
			this.customerDTO.setUsername("ab");
	        String cartJson = json(new CartDTO(this.itemListDTO1,this.customerDTO));
	        this.mockMvc.perform(post("/carts")
	                .contentType(contentType)
	                .content(cartJson))
	        		.andExpect(status().isBadRequest());
	}
	
	@Test
	public void createCartWithoutItems() throws Exception {
			this.itemListDTO1 = new HashSet<ItemToPurchaseDTO>();
	        String cartJson = json(new CartDTO(this.itemListDTO1,this.customerDTO));
	        this.mockMvc.perform(post("/carts")
	                .contentType(contentType)
	                .content(cartJson))
	        		.andExpect(status().isBadRequest());
	}
	
	@Test
	public void createCartWithWrongItemFormat() throws Exception {
			this.itemDTO1.setQuantity(0);
	        String cartJson = json(new CartDTO(this.itemListDTO1,this.customerDTO));
	        this.mockMvc.perform(post("/carts")
	                .contentType(contentType)
	                .content(cartJson))
	        		.andExpect(status().isBadRequest());
	}
	
	@Test
    public void readAllCarts() throws Exception {
        mockMvc.perform(get("/carts"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(contentType))
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].id", is(this.cartDTO1.getId())))
                .andExpect(jsonPath("$[0].items", hasSize(this.cartDTO1.getItems().size())))
                .andExpect(jsonPath("$[0].customer.id", is(this.cartDTO1.getCustomer().getId())))
                .andExpect(jsonPath("$[1].items", hasSize(this.cartDTO2.getItems().size())))
                .andExpect(jsonPath("$[1].id", is(this.cartDTO2.getId())))
                .andExpect(jsonPath("$[1].customer.id", is(this.cartDTO2.getCustomer().getId())))
        		.andExpect(jsonPath("$[1].customer.id", is("customer2Id")));
    }
	
	@Test
	public void getCartItems() throws Exception{
		mockMvc.perform(get("/carts/"+cartDTO1.getId()+"/items")
        		.contentType(contentType))
        		.andExpect(status().isOk())
        		.andExpect(jsonPath("$", hasSize(2)))
        		.andExpect(jsonPath("$[0].id", is(this.itemDTO1.getId())))
                .andExpect(jsonPath("$[0].product_id", is(this.itemDTO1.getProduct_id())))
                .andExpect(jsonPath("$[0].quantity", is(this.itemDTO1.getQuantity())))
                .andExpect(jsonPath("$[1].id", is(this.itemDTO2.getId())))
                .andExpect(jsonPath("$[1].product_id", is(this.itemDTO2.getProduct_id())))
                .andExpect(jsonPath("$[1].quantity", is(this.itemDTO2.getQuantity())));
	}
	
	@Test
	public void checkoutCart() throws Exception {
        mockMvc.perform(put("/carts/"+cartDTO1.getId()+"/checkout")
        		.contentType(contentType))
        		.andExpect(status().isOk());
	}
	
	@Test
	public void getCartSpecificItem() throws Exception{
		mockMvc.perform(get("/carts/"+cartDTO1.getId()+"/items/"+itemDTO1.getId())
        		.contentType(contentType))
        		.andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(this.itemDTO1.getId())))
                .andExpect(jsonPath("$.product_id", is(this.itemDTO1.getProduct_id())))
      			.andExpect(jsonPath("$.quantity", is(this.itemDTO1.getQuantity())));
	}
	
	@Test
	public void getCartCustomer() throws Exception{
		mockMvc.perform(get("/carts/"+cartDTO1.getId()+"/customer")
        		.contentType(contentType))
        		.andExpect(status().isOk())
        	    .andExpect(jsonPath("$.id", is(this.customerDTO.getId())))
                .andExpect(jsonPath("$.username", is(this.customerDTO.getUsername())))
                .andExpect(jsonPath("$.firstName", is(this.customerDTO.getFirstName())))
                .andExpect(jsonPath("$.lastName", is(this.customerDTO.getLastName())));
	}



	protected String json(Object o) throws IOException {
	        MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
	        this.mappingJackson2HttpMessageConverter.write(
	                o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
	        return mockHttpOutputMessage.getBodyAsString();
	}

}
